#if !defined(MOUSETRACER_HPP_INCLUDED)
#define MOUSETRACER_HPP_INCLUDED

#include "MouseTracer.h"

#if !defined(_TRACKINGTIPS_H_INCLUDED_)
#include "TrackingTips.h"
#endif	// #if !defined(_TRACKINGTIPS_H_INCLUDED_)

#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL                   0x020A
#endif	// #ifndef WM_MOUSEWHEEL

#if !defined(_TRACKINGTIPS_H_INCLUDED_)
#include "TrackingTips.h"
#endif	// #if !defined(_TRACKINGTIPS_H_INCLUDED_)

template<class TManager>
BOOL CMouseTracer<TManager>::Setup(HINSTANCE hMod /*= NULL*/, DWORD dwThreadID/* = 0*/)
{
	return TManager::SetupMouseTracer(hMod, dwThreadID);
}

template<class TManager>
void CMouseTracer<TManager>::Remove(DWORD dwThreadID/* = 0*/)
{
	TManager::RemoveMouseTracer(dwThreadID);
}

template<class TManager>
CMouseTracer<TManager>::CMouseTracer(BOOL bAutoDelete, BOOL *pbSinkNotifyDeletion/* = NULL*/) : CMessageHook<CMouseTracer<TManager> >(bAutoDelete),
	m_bHasCapture(FALSE),	m_bWheelCmd(FALSE), m_bTempPreemptive(FALSE), m_wCommand(MG::NOCOMMAND),
	m_dwMarkOldTime((DWORD) - 1), m_bCatTarget((BYTE) - 1), m_tchMark(NULL), m_tchMarkOld(NULL),
	m_pattern(_T(' ')), m_pbSinkNotifyDeletion(pbSinkNotifyDeletion),
	m_bSetTimer(FALSE)
{
	if(m_pbSinkNotifyDeletion)
	*m_pbSinkNotifyDeletion = FALSE;
}

template<class TManager>
CMouseTracer<TManager>::~CMouseTracer()
{
	if(m_pbSinkNotifyDeletion)
	*m_pbSinkNotifyDeletion = TRUE;
}

/**
*	CMouseTracer<TManager>::ProcessWindowMessage()
*
*	@param UINT nMsg
*	@param WPARAM wParam
*	@param LPARAM lParam
*	@param LRESULT &lResult[out] not used.
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::ProcessWindowMessage(UINT nMsg, WPARAM wParam, LPARAM lParam, LRESULT &/*lResult*/)
{
	BOOL bHandled = FALSE;
	
	switch (nMsg)
	{
	case WM_LBUTTONDBLCLK:
		bHandled = OnButtonDblClk(MG::BUTTON_LEFT, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_RBUTTONDBLCLK:
		bHandled = OnButtonDblClk(MG::BUTTON_RIGHT, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_MBUTTONDBLCLK:
		bHandled = OnButtonDblClk(MG::BUTTON_MIDDLE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_RBUTTONDOWN:
	case WM_NCRBUTTONDOWN:
		if (MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnButtonDown(MG::BUTTON_RIGHT, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_RBUTTONUP:
	case WM_NCRBUTTONUP:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnButtonUp(MG::BUTTON_RIGHT, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_MBUTTONDOWN:
	case WM_NCMBUTTONDOWN:
		if (MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnButtonDown(MG::BUTTON_MIDDLE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_MBUTTONUP:
	case WM_NCMBUTTONUP:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnButtonUp(MG::BUTTON_MIDDLE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_LBUTTONDOWN:
	case WM_NCLBUTTONDOWN:
		if (MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnButtonDown(MG::BUTTON_LEFT, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_LBUTTONUP:
	case WM_NCLBUTTONUP:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnButtonUp(MG::BUTTON_LEFT, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_MOUSEWHEEL:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnMouseWheel((int)(short)HIWORD(wParam));
		break;
		
	case WM_MOUSEMOVE:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
		{
			if(!CheckMGAButtonDown((UINT)wParam))
				break;
		}
		// fall through...
	case WM_NCMOUSEMOVE:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnMouseMove((int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
		break;
		
	case WM_CAPTURECHANGED:
		if (m_bHasCapture && MGCfg::Get().Option().IsMGEnabled())
			bHandled = OnCaptureChanged((HWND)lParam);
		break;
	}
	
	return bHandled;
}

/**	@brief The CMouseTracer<TManager>::OnButtonDblClk() function
*	
*	@param	nButton[in]	a parameter of type UINT 
*	@param	x[in]	a parameter of type int 
*	@param	y[in]	a parameter of type int 
*
*	@return BOOL
*/
template<class TManager>
BOOL CMouseTracer<TManager>::OnButtonDblClk(enum MG::Button button, int x, int y)
{
	if (MGCfg::Get().Option().GetMGAButton() == button)
	{
		return ActivateMouseGesture(x, y);
	}
	else
	{
		if (!m_bHasCapture)
		{
			if (MGCfg::Get().Option().IsDblClkScrollingEnabled())
			{
				if( MGCfg::Get().Option().IsMiddleButtonForDblClkScrolling() )
				{
					if(MG::BUTTON_MIDDLE == button && MGCfg::Get().Option().GetMGAButton() != button)
						return DoubleClickScrolling(x, y);
				}
				else
				{
					if(MG::BUTTON_LEFT == button)
						return DoubleClickScrolling(x, y);
				}
			}

			if( MG::BUTTON_LEFT != button && !MGCfg::Get().Option().IsPreemptiveMGAButtonAlways() )
			{
				m_bTempPreemptive = !m_bTempPreemptive;
				MGCfg::Get().Action().Notify(m_bTempPreemptive ? MG::MGAB_PREEMPTIVE_ON : MG::MGAB_PREEMPTIVE_OFF);
			}
		}
		else
		{
			TCHAR tchMark = NULL;
			if (MG::BUTTON_LEFT == button)
				tchMark = MGCfg::Get().List().GetKeyMark(MG::KEY_BUTTON1);
			else
				tchMark = MGCfg::Get().List().GetKeyMark(MG::KEY_BUTTON2);
			
			m_pattern.push_back(tchMark);
			
			std::tstring strCommand;
			
			m_wCommand = MGCfg::Get().List().GetMouseCmd(m_pattern.GetAsString().c_str(), m_bCatTarget);
			if (MG::NOCOMMAND != m_wCommand)
			{
				strCommand = MGCfg::Get().List().GetCommandDesc(m_wCommand).c_str();
			}
			
			if (MGCfg::Get().Option().IsShowRecognitionOnStatusBar())
			{
				std::tostringstream tssMsg;
				tssMsg << m_pattern.GetAsString().c_str() << _T(" ") << strCommand;
				MGCfg::Get().Action().UpdateStatusBar(tssMsg.str().c_str());
			}
			
			m_bWheelCmd = FALSE;
			
			if(MG::BUTTON_LEFT == button && MGCfg::Get().Option().IsPreemptiveMGAButtonAlways())
				return TRUE;	// eat up WM_LBUTTONDBLCLK
		}
	}
	
	return FALSE;
}

/**	@brief The CMouseTracer<TManager>::OnButtonDown() function
*	
*	@param	nButton[in]	a parameter of type UINT 
*	@param	x[in]	a parameter of type int 
*	@param	y[in]	a parameter of type int 
*
*	@return BOOL
*/
template<class TManager>
BOOL CMouseTracer<TManager>::OnButtonDown(enum MG::Button button, int x, int y)
{
	if (MGCfg::Get().Option().GetMGAButton() == button)
	{
		if (!MGCfg::Get().Option().IsMGSuspendingKeyEnabled() || (MGCfg::Get().Option().IsMGSuspendingKeyEnabled() && !IsTemporarySuspended()))
		{
			// synthesize a MGA button down message and fire it off, which in the turn of a hand, forms MGA button double click message
			// ,therefore ActivateMouseGestre() is invoked in OnButtonDblClk() without spoiling the original MGA button down message at all

#if (_WIN32_WINNT > 0x0400)
			
			INPUT mouse_event;
			::ZeroMemory(&mouse_event, sizeof(mouse_event));
			mouse_event.type = INPUT_MOUSE;
			mouse_event.mi.dx = x;
			mouse_event.mi.dy = y;
			
			switch (button)
			{
				case MG::BUTTON_MIDDLE:
					mouse_event.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN;
					break;
				case MG::BUTTON_RIGHT:
					mouse_event.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
					break;
			}
			
			::SendInput(1, &mouse_event, sizeof(INPUT));
			
#else	// #if (_WIN32_WINNT > 0x0400)
			
			switch (button)
			{
				case MG::BUTTON_MIDDLE:
					::mouse_event(MOUSEEVENTF_MIDDLEDOWN, x, y, 0, 0);
					break;
				case MG::BUTTON_RIGHT:
					::mouse_event(MOUSEEVENTF_RIGHTDOWN, x, y, 0, 0);
					break;
			}
			
#endif	// #if (_WIN32_WINNT > 0x0400)
			
			
			if (MGCfg::Get().Option().IsPreemptiveMGAButtonAlways() || m_bTempPreemptive)
				return TRUE;
		}
	}
	else if(m_bHasCapture)
	{
		if(MGCfg::Get().Option().IsHelpTipEnabled())
		{
			CTrackingTips::Activate(FALSE);
			SetHelpTipTimer();
		}

		TCHAR tchMark = NULL;
		if (MG::BUTTON_LEFT == button)
			tchMark = MGCfg::Get().List().GetKeyMark(MG::KEY_BUTTON1);
		else
			tchMark = MGCfg::Get().List().GetKeyMark(MG::KEY_BUTTON2);
		
		m_pattern.push_back(tchMark);
		
		std::tstring strCommand;
		
		m_wCommand = MGCfg::Get().List().GetMouseCmd(m_pattern.GetAsString().c_str(), m_bCatTarget);
		if (m_wCommand != MG::NOCOMMAND)
		{
			strCommand = MGCfg::Get().List().GetCommandDesc(m_wCommand).c_str();
		}
		
		if (MGCfg::Get().Option().IsShowRecognitionOnStatusBar())
		{
			std::tostringstream tssMsg;
			tssMsg << m_pattern.GetAsString().c_str() << _T(" ") << strCommand;
			MGCfg::Get().Action().UpdateStatusBar(tssMsg.str().c_str());
		}
		
		m_bWheelCmd = FALSE;

		if(MG::BUTTON_LEFT == button && MGCfg::Get().Option().IsPreemptiveMGAButtonAlways())
			return TRUE;	// eat up WM_LBUTTONDOWN

	}
	
	return FALSE;
}

/**
*	CMouseTracer<TManager>::OnButtonUp()
*
*	@param UINT nButton
*	@param int x
*	@param int y
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::OnButtonUp(enum MG::Button button, int x, int y)
{
	if (MGCfg::Get().Option().GetMGAButton() == button)
	{
		if(MGCfg::Get().Option().IsHelpTipEnabled())
		{
			KillHelpTipTimer();
			CTrackingTips::Activate(FALSE);
		}

		if (::GetCapture() == GetHwnd())
		{
			::ReleaseCapture();
			m_bHasCapture = FALSE;
			
			TRACE(_T("ReleaseCapture()\n"));
			
			m_ptEnd.x = x;
			m_ptEnd.y = y;
		}
		
		// no valid command has been found
		if (m_wCommand == MG::NOCOMMAND)
		{
			MGCfg::Get().Action().Notify(MG::MG_DEACTIVATE);
			if (MGCfg::Get().Option().IsShowRecognitionOnStatusBar())
				MGCfg::Get().Action().UpdateStatusBar(NULL);

			m_pattern.clear();
			
			if(!m_bWheelCmd)
				MGCfg::Get().Action().NoCommand(GetHwnd(), MG::BUTTON_RIGHT == MGCfg::Get().Option().GetMGAButton(), m_ptStart, m_bCatTarget);
			
			// return TRUE if wheel command has been executed
			return m_bWheelCmd;
		}
		else
		{
			BOOL bExecuted = MGCfg::Get().Action().ExecuteCommand(GetHwnd(), m_wCommand, MGCfg::Get().List().GetCommandID(m_wCommand).c_str(), m_ptStart, MGCfg::Get().List().GetCommandCat(m_wCommand), m_bCatTarget);
			
			MGCfg::Get().Action().Notify(MG::MG_DEACTIVATE);
			if (MGCfg::Get().Option().IsShowRecognitionOnStatusBar())
				MGCfg::Get().Action().UpdateStatusBar(NULL);
			
			m_pattern.clear();
			
			m_wCommand = MG::NOCOMMAND;
			m_bTempPreemptive = FALSE;
			
			return bExecuted;
		}
	}
	else
	{
		if (m_bHasCapture)
			return TRUE;
	}
	
	return TRUE;
}

/**
*	CMouseTracer<TManager>::OnMouseWheel()
*
*	@param int zDelta
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::OnMouseWheel(int zDelta)
{
	if(MGCfg::Get().Option().IsHelpTipEnabled())
	{
		CTrackingTips::Activate(FALSE);
		SetHelpTipTimer();
	}

	m_pattern.clear();
	
	TCHAR tchMark = NULL;
	if (zDelta >= 0)
		tchMark = MGCfg::Get().List().GetKeyMark(MG::KEY_WHEEL_UP);
	else
		tchMark = MGCfg::Get().List().GetKeyMark(MG::KEY_WHEEL_DOWN);
	
	m_pattern.push_back(tchMark);
	
	m_wCommand = MGCfg::Get().List().GetMouseCmd(m_pattern.GetAsString().c_str(), m_bCatTarget);
	
	std::tstring strCommand;
	BOOL bExecuted = FALSE;

	if (m_wCommand != MG::NOCOMMAND)
	{
		// a valid MOUSEWHEEL command
		bExecuted = MGCfg::Get().Action().ExecuteCommand(GetHwnd(), m_wCommand, MGCfg::Get().List().GetCommandID(m_wCommand).c_str(), m_ptStart, MGCfg::Get().List().GetCommandCat(m_wCommand), m_bCatTarget);

		strCommand = MGCfg::Get().List().GetCommandDesc(m_wCommand).c_str();
		
		m_wCommand = MG::NOCOMMAND;
		m_bTempPreemptive = FALSE;
		m_bWheelCmd = TRUE;
	}
	
	m_pattern.clear();
	
	if (MGCfg::Get().Option().IsShowRecognitionOnStatusBar())
		MGCfg::Get().Action().UpdateStatusBarWheelCommand( strCommand.c_str(), (zDelta > 0) );
	
	return bExecuted;
}

/**
*	CMouseTracer<TManager>::CheckMGAButtonDown()
*
*	@param UINT vkDown
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::CheckMGAButtonDown(UINT vkDown)
{
	if(MGCfg::Get().Option().GetMGAButton() & vkDown)
		return TRUE;

	::ReleaseCapture();
	m_bHasCapture = FALSE;
	
	TRACE(_T("ReleaseCapture()\n"));
	
	MGCfg::Get().Action().Notify(MG::MG_ERROR);
	
	return FALSE;
}


/**
*	CMouseTracer<TManager>::OnMouseMove()
*
*	@param int x
*	@param int y
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::OnMouseMove(int x, int y)
{
	m_ptEnd.x = x;
	m_ptEnd.y = y;
	
	if (Distance(m_ptTemp, m_ptEnd) >= MGCfg::Get().Option().GetGestureRecognitionSensitivity())
	{
		m_tchMark = MGCfg::Get().List().GetArrowMark(MG::ArrowDef(GetArrowDirection(m_ptTemp, m_ptEnd)));
		m_ptTemp = m_ptEnd;
		
		if(MGCfg::Get().Option().IsHelpTipEnabled())
		{
			KillHelpTipTimer();
			CTrackingTips::Activate(FALSE);
		}

		if (m_tchMark == m_tchMarkOld)
		{
			CTrackingTips::Activate(FALSE);

			if ((::GetTickCount() - m_dwMarkOldTime) > MGCfg::Get().Option().GetDetectingRepeatedSameDirectionRate())
				m_tchMarkOld = NULL;

			m_dwMarkOldTime = ::GetTickCount();
			return TRUE;
		}
		else
		{
			m_pattern.push_back(m_tchMark);
			m_tchMarkOld = m_tchMark;
			m_dwMarkOldTime = ::GetTickCount();
		}
	}
	else
	{
		if(MGCfg::Get().Option().IsHelpTipEnabled())
		{
			if(!m_bSetTimer)
				SetHelpTipTimer();
		}	
		return TRUE;
	}
	
	if (!m_pattern.empty())
	{
		// TODO: parse command and display the matching command if any exist
		std::tstring strCommand;
		
		m_wCommand = MGCfg::Get().List().GetMouseCmd(m_pattern.GetAsString().c_str(), m_bCatTarget);
		if (m_wCommand != MG::NOCOMMAND)
		{
			strCommand = MGCfg::Get().List().GetCommandDesc(m_wCommand).c_str();
		}
		
		if (MGCfg::Get().Option().IsShowRecognitionOnStatusBar())
		{
			std::tostringstream tssMsg;
			tssMsg << m_pattern.GetAsString().c_str() << _T(" ") << strCommand;
			MGCfg::Get().Action().UpdateStatusBar(tssMsg.str().c_str());
		}
		
		m_bWheelCmd = FALSE;
	}
	
	return TRUE;
}

/**
*	CMouseTracer<TManager>::OnCaptureChanged()
*
*	@param HWND hWndGaining
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::OnCaptureChanged(HWND hWndGaining)
{
	if (hWndGaining != GetHwnd())
	{
		m_bHasCapture = FALSE;
	}
	
	return FALSE;
}

template<class TManager>
BOOL CMouseTracer<TManager>::HasMouseCapture() const
{
	return m_bHasCapture;
}

// wParam of WM_#BUTTONDOWN, WM_#BUTTONUP, WM_#BUTTONDBLCLK or WM_MOUSEMOVE only (#: L, M or R)
/**
*	CMouseTracer<TManager>::IsTemporarySuspended()
*
*	@param WPARAM wParam
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::IsTemporarySuspended(WPARAM wParam) const
{
	return GetVirtualKeyState(MGCfg::Get().Option().GetMGSuspendingKey(), wParam);
}

#define SHIFTED_WORD 0x8000 
/**
*	CMouseTracer<TManager>::GetVirtualKeyState()
*
*	@param int nVirtualKey
*	@param WPARAM wParam
*
*	@return BOOL
*
*/
template<class TManager>
BOOL CMouseTracer<TManager>::GetVirtualKeyState(int nVirtualKey, WPARAM wParam) const
{
	if (nVirtualKey == MG::VIRTUAL_SHIFT && wParam)
	{
		if (wParam & MK_SHIFT)
			return TRUE;
		else 
			return FALSE;
	}
	else if (nVirtualKey == MG::VIRTUAL_CONTROL && wParam)
	{
		if (wParam & MK_CONTROL)
			return TRUE;
		else 
			return FALSE;
	}
	
	SHORT nKeyState = ::GetKeyState(nVirtualKey);
	
	return (nKeyState & SHIFTED_WORD);
}

template<class TManager>
BOOL CMouseTracer<TManager>::ActivateMouseGesture(int x, int y)
{
	m_bWheelCmd = FALSE;
	m_wCommand = MG::NOCOMMAND;
	m_pattern.clear();
	
	if (m_bHasCapture)
	{
		::ReleaseCapture();
		m_bHasCapture = FALSE;

		TRACE(_T("ReleaseCapture()\n"));
		
		MGCfg::Get().Action().Notify(MG::MG_ERROR);
		
		return FALSE;
	}
	
	m_bHasCapture = TRUE;
	HWND hWndPrevCapture = ::SetCapture(GetHwnd());
	if (hWndPrevCapture)
	{
		// one of the the window that belongs to the current thread have already captured the mouse by invoking SetCapture()
		// let's not ruin the application's original behavior so we are simply setting mouse capture back to the original window then return
		m_bHasCapture = FALSE;
		::SetCapture(hWndPrevCapture);
		return FALSE;
	}
	TRACE(_T("SetCapture()\n"));

	MGCfg::Get().Action().Notify(MG::MG_ACTIVATE);
	
	m_ptStart.x = x;
	m_ptStart.y = y;
	
	m_ptEnd = m_ptTemp = m_ptStart;

	if(MGCfg::Get().Option().IsHelpTipEnabled())
		SetHelpTipTimer();
	
	return TRUE;
}

template<class TManager>
BOOL CMouseTracer<TManager>::DoubleClickScrolling(int/* x*/, int y)
{
	RECT rcWnd = { NULL };
	::GetClientRect(GetHwnd(), &rcWnd);
	UINT nVertPosPercent = (y - rcWnd.top) * 100 / (rcWnd.bottom - rcWnd.top);
	UINT nPageTopBottomRecognitionArea = MGCfg::Get().Option().GetPageTopBottomRecognitionArea();
	
	enum MG::DoubleClickOption option(MG::DOUBLE_CLICK_NONE);
	
	if( MGCfg::Get().Option().IsPageUpDown() )
	{
		if (nVertPosPercent <= 50)
			option = MG::DOUBLE_CLICK_PAGE_UP;				// Page Up
		else
			option = MG::DOUBLE_CLICK_PAGE_DOWN;			// Page Down
	}
	
	if( MGCfg::Get().Option().IsPageTopBottom() )
	{
		if (nVertPosPercent <= nPageTopBottomRecognitionArea)
			option = MG::DOUBLE_CLICK_PAGE_TOP;			// Page Top
		else if (nVertPosPercent >= 100 - nPageTopBottomRecognitionArea)
			option = MG::DOUBLE_CLICK_PAGE_BOTTOM;		// Page Bottom
	}
	
	return MGCfg::Get().Action().ExecuteDblClk(GetHwnd(), MG::DoubleClickOption(option), m_ptStart, m_bCatTarget);
}

/**
*	CMouseTracer<TManager>::Distance()
*
*	@param const POINT & rptOne
*	@param const POINT & rptTwo
*
*	@return UINT
*
*/
template<class TManager>
UINT CMouseTracer<TManager>::Distance(const POINT &rptOne, const POINT &rptTwo) const
{
	POINT ptDiff = { rptOne.x - rptTwo.x, rptOne.y - rptTwo.y };
	
	return (UINT)sqrt(ptDiff.x * ptDiff.x + ptDiff.y * ptDiff.y);
}

#define	PI						3.141592653589793238462643383279643328
#define RADIAN_TO_ANGLE(a)		((a) / PI * 180.0)
/**
*	CMouseTracer<TManager>::GetArrowDirection()
*
*	@param const POINT & rptStart
*	@param const POINT & rptEnd
*
*	@return UINT
*
*/
template<class TManager>
UINT CMouseTracer<TManager>::GetArrowDirection(const POINT &rptStart, const POINT &rptEnd) const
{
	LONG xlen = rptEnd.x - rptStart.x;
	LONG ylen = rptStart.y - rptEnd.y;
	
	if (labs(xlen) < 1)
		xlen = 0;
	if (labs(ylen) < 1)
		ylen = 0;
	
	if (xlen == 0 && ylen == 0)
		return MG::ARROW_RIGHT;
	
	if (xlen == 0)
	{
		if (ylen > 0)
			return MG::ARROW_UP;
		else		
			return MG::ARROW_DOWN;
	}
	
	if (ylen == 0)
	{
		if (xlen > 0)
			return MG::ARROW_RIGHT;
		else		
			return MG::ARROW_LEFT;
	}
	
	int nAngle = (int)RADIAN_TO_ANGLE(atan2((double)ylen, (double)xlen));
	while (nAngle < 0) nAngle += 360;
	
	if (nAngle <   45 || nAngle >  315)
		return MG::ARROW_RIGHT;
	else if (nAngle >=  45 && nAngle <= 135)
		return MG::ARROW_UP;
	else if (nAngle >  135 && nAngle <  225)
		return MG::ARROW_LEFT;
	else if (nAngle >= 225 && nAngle <= 315)
		return MG::ARROW_DOWN;
	
	return MG::ARROW_RIGHT;
}

template<class TManager>
void CMouseTracer<TManager>::SetHelpTipTimer()
{
	m_thunkTimer.Init(CMouseTracer<TManager>::TimerProc, this);
	TIMERPROC pProcTimer = (TIMERPROC)&(m_thunkTimer.thunk);
	::SetTimer(GetHwnd(), TTIMER_ID, MGCfg::Get().Option().GetDelayTimeHelpTip(), pProcTimer);
	m_bSetTimer = TRUE;
}

template<class TManager>
void CMouseTracer<TManager>::KillHelpTipTimer()
{
	::KillTimer(GetHwnd(), TTIMER_ID);
	m_bSetTimer = FALSE;
}

template<class TManager>
void CALLBACK CMouseTracer<TManager>::TimerProc(HWND hwnd, UINT /*uMsg*/, UINT_PTR idEvent, DWORD /*dwTime*/)
{
	CMouseTracer<TManager> *pThis = (CMouseTracer<TManager> *)hwnd;
	::KillTimer(pThis->GetHwnd(), idEvent);
	//	pThis->m_bSetTimer = FALSE;

	CTrackingTips::AddTool(pThis->GetHwnd());

	std::tostringstream tss;

	int nGesture = MGCfg::Get().List().GetCount();
	for(int i = 0; i < nGesture; i++)
	{
		if(!MGCfg::Get().List().Get(i)->pattern.GetAsString().find(pThis->m_pattern.GetAsString()))
			tss << _T("   ") << MGCfg::Get().List().Get(i)->pattern.GetAsString().c_str() << _T("   (") << MGCfg::Get().List().Get(i)->pszID << _T(")   ") << std::endl;
	}

	std::tstring strTipText = tss.str().c_str();
	int nLenTipText = strTipText.size();
	if(nLenTipText > 0)
		strTipText.erase(nLenTipText - 1);

	CTrackingTips::UpdateTipText(strTipText.c_str());

	POINT ptCursor;
	::GetCursorPos(&ptCursor);

	ICONINFO ii;
	GetIconInfo(GetCursor(), &ii);

	CTrackingTips::Position(ptCursor.x + ::GetSystemMetrics(SM_CXCURSOR) - ii.xHotspot, ptCursor.y/* + ::GetSystemMetrics(SM_CYCURSOR) - ii.yHotspot*/);
	
	CTrackingTips::Activate(TRUE);
}

#endif	// #if !defined(MOUSETRACER_HPP_INCLUDED)
